package com.ivo.security.filter;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.filter.OncePerRequestFilter;

import com.ivo.model.hr.Employee;
import com.ivo.security.authenticator.Authenticator;
import com.ivo.security.authenticator.AuthenticatorException;
import com.ivo.security.config.SecurityConfig;
import com.ivo.security.interceptor.LoginInterceptor;
import com.ivo.util.Util;

/**
 * This is a filter that logs the user in. It works a little like J2EE
 * form-based seraph, except it looks for the parameters 'os_username' and
 * 'os_password' instead of j_username and j_password.
 * <p>
 * The form post/get action should be the URL of the login servlet/JSP/action -
 * given by SecurityFilter.LOGIN_URL.
 * <p>
 * If the parameters exist and authentication is successful, the user will be
 * redirected by the filter to the URL given by the session attribute at
 * SecurityFilter.ORIGINAL_URL_KEY.
 * <p>
 * If this URL doesn't exist, it will look for a parameter 'os_destination' to
 * use as the redirected URL instead.
 * <p>
 * If neither is found, it is assumed that the page will check the authorisation
 * status and handle redirection itself.
 * <p>
 * From the any other filter in the request, or the servlet/JSP/action which
 * processes the request, you can look up the status of the authorisation
 * attempt. The status is a String request attribute, with the key
 * 'os_authstatus'.
 * <p>
 * The possible statuses are:
 * <ul>
 * <li> LoginFilter.LOGIN_SUCCESS - the login was processed, and user was logged
 * in
 * <li> LoginFilter.LOGIN_FAILURE - the login was processed, the user gave a bad
 * username or password
 * <li> LoginFilter.LOGIN_ERROR - the login was processed, an exception occurred
 * trying to log the user in
 * <li> LoginFilter.LOGIN_NOATTEMPT - the login was no processed, no form
 * parameters existed
 * </ul>
 */
public class LoginFilter extends OncePerRequestFilter
{
	private Log log = LogFactory.getLog( this.getClass() );

	public static final String ALREADY_FILTERED = "loginfilter.already.filtered";

	public static final String LOGIN_SUCCESS = "success";
	public static final String LOGIN_FAILED = "failed";
	public static final String LOGIN_ERROR = "error";
	public static final String LOGIN_NOATTEMPT = null;
	public static final String AUTHSTATUS_KEY = "authstatus";
	public static final String ORIGINAL_URL_KEY = "security_originalurl";

	public void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain ) throws IOException, ServletException {
		// wrap the request with one that returns the LoginUser as the Principal
		request = new SecurityHttpRequestWrapper( request );

		HttpSession session = request.getSession();
		Employee user = (Employee) session.getAttribute( "LOGIN_USER" );

		if( request.getAttribute( ALREADY_FILTERED ) != null || !getSecurityConfig( request ).getController().isSecurityEnabled() ) {
			chain.doFilter( request, response );
			return;
		} else {
			request.setAttribute( ALREADY_FILTERED, Boolean.TRUE );
		}

		request.setAttribute( AUTHSTATUS_KEY, LOGIN_NOATTEMPT );
		// check for parameters
		String username = request.getParameter( "username" );
		String password = request.getParameter( "password" );
		boolean persistentLogin = "true".equals( request.getParameter( "cookie" ) );

		boolean loggedIn = false;

		// try to login the user if possible
		if( username != null && password != null ) {
			List interceptors = getSecurityConfig( request ).getInterceptors( LoginInterceptor.class );
			try {
				for( Iterator iterator = interceptors.iterator(); iterator.hasNext(); ) {
					LoginInterceptor loginInterceptor = (LoginInterceptor) iterator.next();
					loginInterceptor.beforeLogin( request, response, username, password, persistentLogin );
				}
				password = Util.getUTF8HEX( password );

				loggedIn = getAuthenticator( request ).login( request, response, username, password, persistentLogin );

				if( loggedIn ) {
					log.debug( "Login was successful - setting attribute to \"Success\"" );
					request.setAttribute( AUTHSTATUS_KEY, LOGIN_SUCCESS );
				} else {
					log.debug( "Login was not successful - setting attribute to \"Failed\"" );
					request.setAttribute( AUTHSTATUS_KEY, LOGIN_FAILED );
				}
			} catch ( AuthenticatorException e ) {
				log.debug( "Login was not successful, and exception was thrown - setting attribute to \"Error\"" );
				request.setAttribute( AUTHSTATUS_KEY, LOGIN_ERROR );
				e.printStackTrace();
				log.warn( "Exception was thrown whilst logging in: " + e.getMessage(), e );
			}

			for( Iterator iterator = interceptors.iterator(); iterator.hasNext(); ) {
				LoginInterceptor loginInterceptor = (LoginInterceptor) iterator.next();
				loginInterceptor.afterLogin( request, response, username, password, persistentLogin, (String) request.getAttribute( AUTHSTATUS_KEY ) );
			}

		} else if( user != null ) {
			loggedIn = true;
			request.setAttribute( ALREADY_FILTERED, Boolean.TRUE );
		}

		String strLoginURLKey = (String) getSecurityConfig( request ).getLoginURLKey();
		String strLoginURL = strLoginURLKey.substring( strLoginURLKey.lastIndexOf( "/" ) + 1, strLoginURLKey.length() );

		// if we successfully logged in - look for an original URL to forward to
		if( loggedIn ) {
			String originalURL = (String) getSecurityConfig( request ).getOriginalURLKey();

			String url = request.getRequestURI();
			url = url.substring( url.lastIndexOf( "/" ) + 1, url.length() );

			if( originalURL != null ) {
				if( log.isDebugEnabled() ) log.debug( "Logged In - Redirecting to Original URL: " + request.getContextPath() + originalURL );

				request.getSession().setAttribute( ORIGINAL_URL_KEY, null );
				if( url.equals( strLoginURL ) || url.equals( "" ) ) response.sendRedirect( request.getContextPath() + originalURL );
			} else if( request.getParameter( "destination" ) != null ) {
				if( log.isDebugEnabled() ) log.debug( "Logged In - redirecting to destination: " + request.getContextPath() + request.getParameter( "destination" ) );

				response.sendRedirect( request.getContextPath() + request.getParameter( "destination" ) );
				return;
			}
		} else {
			String url = request.getRequestURI();
			url = url.substring( url.lastIndexOf( "/" ) + 1, url.length() );
			if( !url.equals( strLoginURL ) ) response.sendRedirect( request.getContextPath() + strLoginURLKey );
		}

		// System.out.println(request.getContextPath()+",
		// URI:"+request.getRequestURI());
		chain.doFilter( request, response );
	}

	protected SecurityConfig getSecurityConfig( HttpServletRequest request ) {
		SecurityConfig securityConfig = (SecurityConfig) request.getSession().getServletContext().getAttribute( SecurityConfig.STORAGE_KEY );
		return securityConfig;
	}

	protected Authenticator getAuthenticator( HttpServletRequest request ) {
		return getSecurityConfig( request ).getAuthenticator();
	}

}
